package myatm;

import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

public class ATMTest {

    double balanceOnAccount;
    static ATM tATM;
    static Card tCard;
    static Account tAccount;

    @Before
    public void init(){
        tATM = new ATM(1000);
        tCard = mock(Card.class);
        tAccount = mock(Account.class);
        balanceOnAccount = 100;

        when(tCard.checkPin(5555)).thenReturn(true);
        when(tCard.checkPin(0000)).thenReturn(false);
        when(tCard.isBlocked()).thenReturn(false);
        when(tCard.getAccount()).thenReturn(tAccount);

        when(tAccount.getBalance()).thenAnswer(new Answer(){
            @Override
            public Object answer(InvocationOnMock invocation){
                return balanceOnAccount;
            }
        });
        when(tAccount.withdrow(50)).thenAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                balanceOnAccount -= 50;
                return balanceOnAccount;
            }
        });

    }

    @Test (expected = ATM.NegativeMoneyInATM.class)
    public void ATM_MoneyInATM_NegativeMoneyInATMThrows(){
        ATM atm = new ATM(-5.0);
    }

    @Test
    public void getMoneyInATM_MoneyInATM_ReturnsCorrectValue(){
        assertEquals(1000, tATM.getMoneyInATM(), 0.001);
    }

    @Test
    public void validateCard_PinIsWrong_ReturnsFalse(){
        assertFalse(tATM.validateCard(tCard, 0000));
    }

    @Test
    public void validateCard_PinIsRightANDCardIsBlocked_ReturnsFalse(){
        when(tCard.isBlocked()).thenReturn(true);
        assertFalse(tATM.validateCard(tCard, 5555));
    }

    @Test (expected = ATM.NoCardInserted.class)
    public void checkBalance_CardIsNotValidated_NoCardInsertedThrown(){
        tATM.checkBalance();
    }

    @Test
    public void checkBalance_CardIsValidated_ReturnsCorrectValue(){
        tATM.validateCard(tCard, 5555);
        assertEquals(100, tATM.checkBalance(), 0.001);
    }

    @Test (expected = ATM.NoCardInserted.class)
    public void getCash_NoValidatedCard_NoCardInsertedThrown(){
        tATM.getCash(50);
    }

    @Test (expected = ATM.NegativeAmountTaken.class)
    public void getCash_NegativeAmount_NegativeAmountTakenThrown(){
        tATM.validateCard(tCard, 5555);
        tATM.getCash(-5);
    }


    @Test (expected = ATM.NotEnoughMoneyInAccount.class)
    public void getCash_NotEnoughMoneyInAccount_NotEnoughMoneyInAccountThrown(){
        tATM.validateCard(tCard, 5555);
        tATM.getCash(101.5);
    }

    @Test (expected = ATM.NotEnoughMoneyInATM.class)
    public void getCash_NotEnoughMoneyInATMWhileEnoughMoneyInAccount_NotEnoughMoneyInATMThrown(){
        balanceOnAccount = 2000;
        tATM.validateCard(tCard, 5555);
        tATM.getCash(1500);
    }


    @Test
    public void getCash_AccountBalanceAfterSuccessTransaction_CorrectValue(){
        tATM.validateCard(tCard, 5555);
        tATM.getCash(50);
        assertEquals(50.0, tATM.checkBalance(), 0.001);
    }

    @Test
    public void getCash_CashInATMAfterSuccessTransaction_CorrectValue(){
        tATM.validateCard(tCard, 5555);
        tATM.getCash(50);
        assertEquals(950, tATM.getMoneyInATM(), 0.001);
    }

    @Test
    public void getCash_SequenceWithNegativeAmountTaken_ExceptionBeforeCashOperations(){
        tATM.validateCard(tCard, 5555);
        try {
            tATM.getCash(-50);
        }catch (ATM.NegativeAmountTaken e){
            assertEquals(1000, tATM.getMoneyInATM(), 0.001);
            assertEquals(100, tATM.checkBalance(), 0.01);
            return;
        }
        throw new RuntimeException();
    }

    @Test
    public void getCash_SequenceWithNoCardValidated_ExceptionBeforeCashOperations(){
        try{
            tATM.getCash(50);
        }catch (ATM.NoCardInserted e){
            assertEquals(1000, tATM.getMoneyInATM(), 0.001);
            return;
        }
        throw new RuntimeException();
    }

    @Test
    public void getCash_SequenceWithNotEnoughMoneyOnAccount_ExceptionBeforeCashOperations(){
        tATM.validateCard(tCard, 5555);
        try{
            tATM.getCash(150);
        }catch (ATM.NotEnoughMoneyInAccount e){
            assertEquals(1000, tATM.getMoneyInATM(), 0.001);
            assertEquals(100, tATM.checkBalance(), 0.01);
            return;
        }
        throw new RuntimeException();
    }

    @Test
    public void getCash_SequenceWithNotEnoughMoneyInATM_ExceptionBeforeCashOperations(){
        balanceOnAccount = 2000;
        when(tAccount.withdrow(1500)).thenAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                balanceOnAccount -= 1500;
                return balanceOnAccount;
            }
        });
        tATM.validateCard(tCard, 5555);
        try{
            tATM.getCash(1500);
        }catch (ATM.NotEnoughMoneyInATM e){
            assertEquals(1000, tATM.getMoneyInATM(), 0.001);
            assertEquals(2000, tATM.checkBalance(), 0.01);
            return;
        }
        throw new RuntimeException();
    }

    @Test (expected = ATM.NoCardInserted.class)
    public void getCash_ValidatedCardWasRemovedAndNoNewCardIsValidated_NoCardInsertedThrown(){
        if(tATM.validateCard(tCard, 5555)){
            if(!tATM.validateCard(tCard, 0000)){
                tATM.getCash(50);
            }
        }
    }

    @Test (expected = ATM.NoCardInserted.class)
    public void getBalance_ValidatedCardWasRemovedAndNoNewCardIsValidated_NoCardInsertedThrown(){
        if(tATM.validateCard(tCard, 5555)){
            if(!tATM.validateCard(tCard, 0000)){
                tATM.checkBalance();
            }
        }
    }

}